package org.vaadin.nf4v.core.navigation;

import com.vaadin.ui.Component;
import com.vaadin.ui.Window;
import org.vaadin.nf4v.core.NavigableApplication;
import org.vaadin.nf4v.core.NavigationException;
import org.vaadin.nf4v.core.navigation.event.EventSupport;
import org.vaadin.nf4v.core.navigation.event.NavigationListener;
import org.vaadin.nf4v.core.navigation.event.NavigationState;
import org.vaadin.nf4v.core.navigation.target.NavigationTarget;
import org.vaadin.nf4v.core.navigation.target.NavigationTargetBuilder;
import org.vaadin.nf4v.core.util.NavigationFlash;
import org.vaadin.nf4v.core.util.Params;

import java.io.Serializable;

/**
 * This is the main class to be used for navigation.
 * It provides:
 * 1. shortcuts for page/context navigation
 * 2. ability to register global listeners
 * 3. access to flash scope
 * 4. current context name
 * and some more utilities
 */
public class NavigationManager implements Serializable {
    private EventSupport support = new EventSupport();

    // todo external navigation, native window

    /**
     * Registers navigation listener for all contexts.
     *
     * @param listener listener to register
     * @param states   states to fire on (empty means all events)
     */
    public static void addApplicationNavigationListener(NavigationListener listener, NavigationState... states) {
        // todo add instance methods?
        NavigableApplication.getCurrent().getNavigationManager().support.addNavigationListener(listener, states);
    }

    /**
     * Removes navigation listener for all contexts.
     *
     * @param listener listener to remove
     * @param states   states to remove from (empty means all states)
     */
    public static void removeApplicationNavigationListener(NavigationListener listener, NavigationState... states) {
        NavigableApplication.getCurrent().getNavigationManager().support.removeNavigationListener(listener, states);
    }

    /**
     * Fires navigation request state event.
     *
     * @param request navigation request
     */
    void fireEvent(NavigationRequest request) {
        support.fireEvent(request);
    }

    /**
     * @return current navigation context name, or null if current application window is not navigable window
     */
    public static String getNavigationContext() {
        if (NavigableApplication.getCurrentNavigableWindow() != null) {
            String context = NavigableApplication.getCurrent().getNavigationConfig().resolveNavigationWindowContext(
                    ((Window) NavigableApplication.getCurrentNavigableWindow()).getName());
            // todo checkout for correctness
            if (context == null) {
                return NavigableApplication.getCurrent().getNavigationConfig().getMainContext();
            }
            return context;
        }
        return null;
    }

    /**
     * @param context context to compare
     * @return true if current context is the same as provided, false otherwise
     */
    public static boolean isContext(String context) {
        return context.equalsIgnoreCase(getNavigationContext());
    }

    /**
     * @return navigator from current navigable window or null if current application window is not navigable window
     */
    public static Navigator getNavigator() {
        if (NavigableApplication.getCurrentNavigableWindow() != null) {
            return NavigableApplication.getCurrentNavigableWindow().getNavigator();
        }
        return null;
    }

    /**
     * Navigates to provided target.
     * Shortcut method for direct calling navigation on Navigator.
     *
     * @param target navigation target
     */
    public static void navigateTo(NavigationTarget target) {
        NavigationManager.getNavigator().navigateTo(target);
    }

    /**
     * Navigates to provided page in current context
     *
     * @param pageClass page target page
     */
    public static void navigateTo(Class<? extends Component> pageClass) {
        navigateTo().page(pageClass).go();
    }

    /**
     * Navigates to provided page in provided context
     *
     * @param context   target context
     * @param pageClass target page
     */
    public static void navigateTo(String context, Class<? extends Component> pageClass) {
        navigateTo().context(context).page(pageClass).go();
    }

    /**
     * Navigates to home page of current context.
     */
    public static void navigateToHome(){
        navigateTo().home().go();
    }

    /**
     * Navigates to home page of provided context
     *
     * @param context target context
     */
    public static void navigateTo(String context) {
        if (!isContext(context)) {
            if (!NavigableApplication.getCurrent().getNavigationConfig().isContextExists(context)) {
                throw new NavigationException("Context " + context + " is not registered");
            }
            navigateTo().context(context).go();
        }
    }

    /**
     * Generic method to specify navigation target
     *
     * @return navigation builder
     */
    public static NavigationTargetBuilder navigateTo() {
        if (getNavigator() == null) {
            throw new NavigationException("Can't detect current context");
        }
        return new NavigationTargetBuilder();
    }

    /**
     * @return current navigation target (target of last navigation or target of current navigation if
     *         navigation request is in process)
     */
    public static NavigationTarget getCurrentTarget() {
        if (getNavigator() == null) {
            throw new NavigationException("Can't detect current context");
        }
        return getNavigator().getCurrentTarget();
    }

    /**
     * @return params utility of current navigation target (params of current page if no navigation is in process,
     *         or params from active navigation target)
     */
    public static Params getCurrentParams() {
        return new Params(getCurrentTarget());
    }

    /**
     * Flash is useful for storage of temporal data between requests.
     *
     * @return navigation flash memory object
     */
    public static NavigationFlash flash() {
        return NavigableApplication.getCurrent() != null ? NavigableApplication.getCurrent().getFlash() : null;
    }
}
